﻿Imports System.Data
Imports System.Data.Common


Public Class DbDataService

#Region "Memeber"

    Private _factory As DbProviderFactory
    Private _connection As DbConnection

    Private _ilog As log4net.ILog = log4net.LogManager.GetLogger(GetType(DbDataService))

#End Region
    

#Region "Events"

    Public Event BeforeExecuteNonQuery(ByVal sender As Object, ByRef e As DbOperationEventArgs)
    Public Event AfterExecuteNonQuery(ByVal sender As Object, ByVal e As DbOperationEventArgs)

    Public Event BeforeGetDataTable(ByVal sender As Object, ByRef e As DbOperationEventArgs)
    Public Event AfterGetDataTable(ByVal sender As Object, ByVal e As DbOperationEventArgs)

    Public Event BeforeGetDataSet(ByVal sender As Object, ByRef e As DbOperationEventArgs)
    Public Event AfterGetDataSet(ByVal sender As Object, ByVal e As DbOperationEventArgs)

    Public Event BeforeExecuteScalar(ByVal sender As Object, ByRef e As DbOperationEventArgs)
    Public Event AfterExecuteScalar(ByVal sender As Object, ByVal e As DbOperationEventArgs)

#End Region


    ''' <summary>
    ''' Erstellt eine neue DbDataSerivce
    ''' </summary>
    ''' <param name="connectionName"></param>
    ''' <remarks></remarks>
    Friend Sub New(ByVal connectionName As String, ByVal providerName As String, ByVal connectionString As String)
        _factory = DbProviderFactories.GetFactory(providerName)
        _connection = _factory.CreateConnection()
        _connection.ConnectionString = connectionString
        TryConnect()
    End Sub


    ''' <summary>
    ''' Liefert die beinhaltete DbConnection
    ''' </summary>
    Public ReadOnly Property Connection As DbConnection
        Get
            Return _connection
        End Get
    End Property


    ''' <summary>
    ''' Liefert die Factory für den Provider
    ''' </summary>
    Public ReadOnly Property Factory As DbProviderFactory
        Get
            Return _factory
        End Get
    End Property


    ''' <summary>
    ''' Stellt eine Verbindung zur Datenbank her und schließt diese direkt wieder.
    ''' </summary>
    Private Sub TryConnect()
        _connection.Open()
        _connection.Close()
    End Sub


    ''' <summary>
    ''' Erstellt einen neuen DbParameter
    ''' </summary>
    Public Overloads Function CreateParameter() As DbParameter
        Return _factory.CreateParameter
    End Function


    ''' <summary>
    ''' Erstellt einen neuen DbParameter
    ''' </summary>
    ''' <remarks>Der Value wird mit System.DBNull.Value vorbelegt.</remarks>
    Public Overloads Function CreateParameter(ByVal parameterName As String, ByVal dataType As DbType) As DbParameter
        Return CreateParameter(parameterName, dataType, System.DBNull.Value)
    End Function


    ''' <summary>
    ''' Erstellt einen neuen DbParameter
    ''' </summary>
    Public Overloads Function CreateParameter(ByVal parameterName As String, ByVal dataType As DbType, ByVal value As Object) As DbParameter
        Dim p As DbParameter = _factory.CreateParameter()
        p.ParameterName = parameterName
        p.DbType = dataType
        p.Value = value
        Return p
    End Function


    ''' <summary>
    ''' Läd die Informationen die über den DbCommand abgefragt werden sollen in ein DataSet.
    ''' </summary>
    ''' <param name="cmd">Der Auszuführenden Command</param>
    Public Function GetDataSet(ByVal cmd As DbCommand) As DataSet

        ' alles passiert hier mit dem eventparameter
        Using operationEventArgs As New DbOperationEventArgs(cmd)

            ' "before trigger" equivaluent ausführen
            RaiseEvent BeforeGetDataSet(Me, operationEventArgs)

            ' wenn die abfrage nicht abgebrochen werden soll wird sie ausgeführt
            If Not operationEventArgs.Cancel Then

                ' log mit log4net
                _ilog.DebugFormat("executing command for dataset: {0}", operationEventArgs.Command.CommandText.Trim())

                ' abfragen
                GetDataSet = InternalGetDataSet(operationEventArgs.Command)

                ' "after trigger" equivalent ausführen
                RaiseEvent AfterGetDataSet(Me, operationEventArgs)

            Else

                ' log mit log4net
                _ilog.DebugFormat("abort datadet request: {0}", operationEventArgs.Command.CommandText.Trim())

                GetDataSet = Nothing

            End If

        End Using

    End Function


    ''' <summary>
    ''' Läd die Informationen die über den DbCommand abgefragt werden sollen in ein DataSet.
    ''' </summary>
    ''' <param name="cmd">Der Auszuführenden Command</param>
    ''' <remarks>Diese interne Methode löst keine Events und keine Logs aus.</remarks>
    Protected Function InternalGetDataSet(ByVal cmd As DbCommand) As DataSet
        Dim da As DbDataAdapter = _factory.CreateDataAdapter
        Dim ds As New DataSet
        cmd.Connection = _connection
        da.SelectCommand = cmd
        da.Fill(ds)
        InternalGetDataSet = ds
    End Function


    ''' <summary>
    ''' Läd die Informationen die über den DbCommand abgefragt werden sollen in eine DataTable.
    ''' </summary>
    ''' <param name="cmd">Der Auszuführenden Command</param>
    Public Function GetDataTable(ByVal cmd As DbCommand) As DataTable

        ' alles passiert hier mit dem eventparameter
        Using operationEventArgs As New DbOperationEventArgs(cmd)

            ' "before trigger" equivaluent ausführen
            RaiseEvent BeforeGetDataTable(Me, operationEventArgs)

            ' wenn die abfrage nicht abgebrochen werden soll wird sie ausgeführt
            If Not operationEventArgs.Cancel Then

                ' log mit log4net
                _ilog.DebugFormat("executing command for datatable: {0}", operationEventArgs.Command.CommandText.Trim())

                ' abfragen 
                GetDataTable = InternalGetDataSet(operationEventArgs.Command).Tables(0)

                ' "after trigger" equivalent ausführen
                RaiseEvent AfterGetDataTable(Me, operationEventArgs)

            Else

                ' log mit log4net
                _ilog.DebugFormat("abort datatable request: {0}", operationEventArgs.Command.CommandText.Trim())

                GetDataTable = Nothing

            End If

        End Using

    End Function


    ''' <summary>
    ''' Führt die Abfrage aus und gibt die erste Spalte der ersten Zeile im Resultset zurück, das durch die Abfrage zurückgegeben wird. Alle anderen Spalten und Zeilen werden ignoriert.
    ''' </summary>
    ''' <param name="cmd">Der auszuführende Befehl</param>
    ''' <returns>Die erste Spalte der ersten Zeile im Resultset.</returns>
    Public Overridable Function ExecuteScalar(ByVal cmd As DbCommand) As Object

        ' alles passiert hier mit dem eventparameter
        Using operationEventArgs As New DbOperationEventArgs(cmd)

            ' "before trigger" equivalent auslösen
            RaiseEvent BeforeExecuteScalar(Me, operationEventArgs)

            ' wenn die aktion nicht abgebrochen werden soll
            If Not operationEventArgs.Cancel Then

                ' log mit log4net
                _ilog.DebugFormat("executing command for scalar: {0}", operationEventArgs.Command.CommandText.Trim())

                ' die scalar funktion ausführen 
                operationEventArgs.Command.Connection = _connection
                _connection.Open()

                Try
                    ExecuteScalar = operationEventArgs.Command.ExecuteScalar()
                    _connection.Close()
                Catch ex As Exception

                    ' wenn ein fehler auftritt, erstmal die verbindung schließen
                    ' anschließend dann den fehler selber werfen.
                    _connection.Close()

                    Throw ex

                End Try


                ' "after trigger" equivalent ausführen
                RaiseEvent AfterExecuteScalar(Me, operationEventArgs)

            Else

                ' log mit log4net
                _ilog.DebugFormat("abort datatable request: {0}", operationEventArgs.Command.CommandText.Trim())

                ExecuteScalar = Nothing

            End If

        End Using

    End Function


    ''' <summary>
    ''' Führt eine SQL-Anweisung für ein ein Verbindungsobjekt aus.
    ''' </summary>
    ''' <param name="cmd">Die Anzahl der betroffenen Zeilen.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ExecuteNonQuery(ByVal cmd As DbCommand) As Integer

        ' alles passiert hier mit dem eventparameter
        Using operationEventArgs As New DbOperationEventArgs(cmd)

            ' "before trigger" equivalent auslösen
            RaiseEvent BeforeExecuteNonQuery(Me, operationEventArgs)

            ' wenn die aktion nicht abgebrochen werden soll
            If Not operationEventArgs.Cancel Then

                ' log mit log4net
                _ilog.DebugFormat("executing command for nonquery: {0}", operationEventArgs.Command.CommandText.Trim())

                ' die scalar funktion ausführen 
                operationEventArgs.Command.Connection = _connection
                _connection.Open()
                Try
                    ExecuteNonQuery = operationEventArgs.Command.ExecuteNonQuery()
                    _connection.Close()
                Catch ex As Exception

                    ' wenn ein fehler auftritt, erstmal die verbindung schließen
                    ' anschließend dann den fehler selber werfen.
                    _connection.Close()

                    Throw ex

                End Try


                ' "after trigger" equivalent ausführen
                RaiseEvent AfterExecuteNonQuery(Me, operationEventArgs)

            Else

                ' log mit log4net
                _ilog.DebugFormat("abort datatable request: {0}", operationEventArgs.Command.CommandText.Trim())

                ExecuteNonQuery = Nothing

            End If

        End Using

    End Function

End Class
